#include <iostream>
#include <ctime>
using namespace std;
class Quaternion;
class RealMatrix;
class Factory;
class Suicidal;
class Complex;
class ComplexMatrix;
class GenericMatrix;

class Suicidal {
	public:
		void die();
};

class GenericMatrix {

};

class Factory {
	public:
		static Quaternion * createNewQuaternion();
		static Quaternion * createFromRealMatrix(RealMatrix *);
		static Quaternion * createFromValues(double, double, double, double);
		static Quaternion * createMatrixProduct(Quaternion *, Quaternion *);
		static Quaternion * updateQuaternion(Quaternion *);
		static RealMatrix * createNewRealMatrix();
		static RealMatrix * copyRealMatrix(RealMatrix *);
		static RealMatrix * createMatrixProduct(RealMatrix *, RealMatrix *);
		static Complex * createNewComplex();
		static Complex * createNewComplex(double, double);
		static Complex * copyComplex(Complex *);
		static ComplexMatrix * createNewComplexMatrix();
		static ComplexMatrix * copyComplexMatrix(ComplexMatrix *);
};

class Calc : public Suicidal {
    public:
        //I have to do this later
    private:
        //ditto
};

class Complex : public Suicidal {
	public:
		friend ostream & operator << (ostream& output, const Complex * p);
		Complex & operator=(Complex * p);
		Complex & operator=(int);
		Complex & operator+=(Complex * p);
		Complex & operator*(Complex * p);
		double getA();
		double getB();
		void setA(double);
		void setB(double);
		friend class Factory;
	private:
		Complex() {
		  a = NULL;  //a
		  b = NULL;  //b(i)
		}
		double * a; //a
		double * b; //b(i)
};

class ComplexMatrix : public Suicidal, public GenericMatrix {
	public:
		void init(int, int);
		Complex * getVal(int, int);
		void setVal(int, int, Complex *);
	private:
		Complex * array[2][2];
		void testPair(int, int);
};

class RealMatrix : public Suicidal, public GenericMatrix {
	public:
		void init(int, int);
		double getVal(int, int);
		void setVal(int, int, double);
		void setArray(double [4][4]);
	private:
		double * array[4][4];
		void testPair(int, int);
};

class Quaternion : public Suicidal {
	public:
		RealMatrix * getRealMatrix();
		ComplexMatrix * getComplexMatrix();
		void setRealMatrix(RealMatrix *);
		void setComplexMatrix(ComplexMatrix *);
		void displayRM();
	private:
		RealMatrix * realMatrix;
		ComplexMatrix * complexMatrix;
};

void Suicidal::die() {
	delete this;
}

RealMatrix * Quaternion::getRealMatrix() {
	return this->realMatrix;
}

void Quaternion::setRealMatrix(RealMatrix * incoming) {
	(this->realMatrix) = Factory::copyRealMatrix(incoming);
}

void Quaternion::setComplexMatrix(ComplexMatrix * p) {
	(this->complexMatrix) = Factory::copyComplexMatrix(p);
}

void Quaternion::displayRM() {
	for(int i = 0; i < 4; i++) {
			for(int j = 0; j < 4; j++) {
					cout<<this->realMatrix->getVal(i,j)<<" ";
				}
			cout<<endl;
		}
}

ComplexMatrix * Quaternion::getComplexMatrix() {
	return this->complexMatrix;
}

void RealMatrix::init(int i, int j) {
	this->array[i][j] = new double;
	*(this->array[i][j]) = 0;
}

void ComplexMatrix::init(int i, int j) {
	this->array[i][j] = Factory::createNewComplex();
	*(this->array[i][j]) = 0;
}

void ComplexMatrix::testPair(int i, int j) {
	if ((i < 0) || (i > 3) || (j < 0) || (j > 3))
		cerr<<" testPair ERROR";
}

Complex * ComplexMatrix::getVal(int i, int j) {
	this->testPair(i,j);
	return this->array[i][j];
}

void ComplexMatrix::setVal(int i, int j, Complex * p) {
	this->testPair(i,j);
	*(this->array[i][j]) = *p;
}

ComplexMatrix * Factory::createNewComplexMatrix() {
	ComplexMatrix * temp = new ComplexMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->init(i,j);
					//temp->array[i][j] = Factory::createNewComplex();
					//*(temp->array[i][j]) = 0;
				}
		}
	return temp;
}

double RealMatrix::getVal(int i, int j) {
	testPair(i,j);
	return *(this->array[i][j]);
}

void RealMatrix::setVal(int i, int j, double newVal) {
	testPair(i,j);
	*(this->array[i][j]) = newVal;
}

void RealMatrix::testPair(int i, int j) {
	if ((i < 0) || (i > 3) || (j < 0) || (j > 3))
		cerr<<" testPair ERROR";
}

void RealMatrix::setArray(double incoming[4][4]) {
	for(int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			this->setVal(i, j, (incoming[i][j]));
}

RealMatrix * Factory::createNewRealMatrix() {
	RealMatrix * temp = new RealMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->init(i,j);
				}
		}
	return temp;
}

Quaternion * Factory::createMatrixProduct(Quaternion * factor1, Quaternion * factor2) {
	Quaternion * temp = Factory::createFromRealMatrix(createMatrixProduct(factor1->getRealMatrix(), factor2->getRealMatrix()));
	return temp;
}

RealMatrix * Factory::copyRealMatrix(RealMatrix * incoming) {
	RealMatrix * temp = Factory::createNewRealMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->setVal(i, j, incoming->getVal(i,j));
				}
		}
	return temp;
}

Quaternion * Factory::createNewQuaternion() {
	return new Quaternion();
}

ComplexMatrix * Factory::copyComplexMatrix(ComplexMatrix * p) {
	ComplexMatrix * temp = Factory::createNewComplexMatrix();
	for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
					temp->setVal(i, j, p->getVal(i,j));
				}
		}
	return temp;
}

Quaternion * Factory::createFromRealMatrix(RealMatrix * incoming) {
	Quaternion * temp = Factory::createNewQuaternion();
	temp->setRealMatrix(Factory::copyRealMatrix(incoming));
	return temp;
}

Quaternion * Factory::createFromValues(double a, double b, double c, double d) {
	//double na = -1 * a;
	double nb = -1 * b;
	double nc = -1 * c;
	double nd = -1 * d;
	double array[4][4] = {{a,b,c,d},{nb,a,nd,c},{nc,d,a,nb},{nd,nc,b,a}};
	RealMatrix * rm_temp = Factory::createNewRealMatrix();
	rm_temp->setArray(array);
	Quaternion * q_temp = Factory::createFromRealMatrix(rm_temp);
	return q_temp;
}

RealMatrix * Factory::createMatrixProduct(RealMatrix * factor1, RealMatrix * factor2) {
	RealMatrix * product = Factory::createNewRealMatrix();
	double * cellVal = 0;
	for(int producti = 0; producti < 4; producti++) { //row
			for(int productj = 0; productj < 4; productj++) { //column
					cellVal = new double;
					*cellVal = 0;
					for(int i = 0; i <4; i++)//inner
						*cellVal += (factor1->getVal(producti, i))*(factor2->getVal(i, productj));
					product->setVal(producti, productj, *cellVal);
					delete cellVal;
				}
		}
	return product;
};

ostream & operator << (ostream& output, Complex * p) {
	output << p->getA() << " + " << p->getB() << "i" << endl;
	return output;
}

Complex * Factory::createNewComplex() {
	Complex * temp = new Complex();
	temp->setA(0);
	temp->setB(0);
	return temp;
}

Complex * Factory::createNewComplex(double a, double b) {
	Complex * temp = Factory::createNewComplex();
	temp->setA(a);
	temp->setB(b);
	return temp;
}

Complex * Factory::copyComplex(Complex * p) {
	Complex * temp = Factory::createNewComplex();
	*temp = *p;
	return temp;
}

Quaternion * Factory::updateQuaternion(Quaternion * p) {
	Quaternion * temp = Factory::createNewQuaternion();
	RealMatrix * itsRealMatrix = p->getRealMatrix();
	ComplexMatrix * itsComplexMatrix;
	itsComplexMatrix = p->getComplexMatrix();
	if (itsComplexMatrix == 0)
		itsComplexMatrix = Factory::createNewComplexMatrix();
	/*double a = itsRealMatrix->getVal(0,0);
	double b = itsRealMatrix->getVal(0,1);
	double c = itsRealMatrix->getVal(0,2);
	double d = itsRealMatrix->getVal(0,3);
	itsComplexMatrix->setVal(0,0,Factory::createNewComplex(a,b));
	itsComplexMatrix->setVal(0,1,Factory::createNewComplex(c,d));
	itsComplexMatrix->setVal(1,0,Factory::createNewComplex((-1 * a), b));
	itsComplexMatrix->setVal(1,1,Factory::createNewComplex(a, (-1 * b)));*/
	//return Factory::createNewQuaternion();
	return temp;
}

double Complex::getA() {
	return *(this->a);
}

double Complex::getB() {
	return *(this->b);
}

void Complex::setA(double newA) {
	if (this->a != 0)
        delete this->a;
    this->a = new double;
	*(this->a) = newA;
}

void Complex::setB(double newB) {
    if (this->b !=0)
        delete this->b;
    this->b = new double;
	*(this->b) = newB;
}

Complex & Complex::operator=(Complex * p) {
	this->setA(p->getA());
	this->setB(p->getB());
	return *this;
};

Complex & Complex::operator=(int p) {
	if (p == 0) {
			;
			this->setA(0);
			this->setB(0);
		}
	else {
			cerr<<"Operator <complex> = 0 error"<<endl;
		}
	return *this;
};

Complex & Complex::operator+=(Complex * p) {
	if (this != p) {
			this->setA(this->getA() + p->getA());
			this->setB(this->getB() + p->getB());
		}
	return *this;
}

Complex & Complex::operator*(Complex * p) {
	double * x = new double;
	double * y = new double;
	double * u = new double;
	double * v = new double;
	double * coreal = new double;
	double * coim = new double;
	*x = this->getA();
	*y = this->getB();
	*u = p->getA();
	*v = p->getB();
	*coreal = ((*x)*(*u))-((*y)*(*v));
	*coim   = ((*x)*(*v))+((*y)*(*u));
	Complex * temp = Factory::createNewComplex(*coreal, *coim);
	delete x;
	delete y;
	delete u;
	delete u;
	delete coreal;
	delete coim;
	return *temp;
}

int main() {
	//clock_t t;
	//t = clock();
	//Quaternion * myQuaternion;
	/*for(long i = 0; i < 500000; i++){
	    Quaternion * myQuaternion1 = Factory::createFromValues(6,0,0,0);
	    Quaternion * myQuaternion2 = Factory::createFromValues(2,3,4,5);
	    myQuaternion = Factory::createMatrixProduct(myQuaternion1, myQuaternion2);
	    t = clock() - t;
	    //myQuaternion = Factory::updateQuaternion(myQuaternion1);
	    //myQuaternion->displayRM();
	    myQuaternion->die();
	    myQuaternion1->die();
	    myQuaternion2->die();
	}*/
	/*for(int trial = 0; trial < 1; trial++){
	    for(long i = 0; i < 500000; i++){
	        Quaternion * myQuaternion1 = Factory::createFromValues(i,i+1,i+2,i+3);
	        Quaternion * myQuaternion2 = Factory::createFromValues(2*i,2*i+1,2*i+2,2*i+3);
	        myQuaternion = Factory::createMatrixProduct(myQuaternion1, myQuaternion2);
	        t = clock() - t;
	        //myQuaternion = Factory::updateQuaternion(myQuaternion1);
	        //myQuaternion->displayRM();
	        myQuaternion->die();
	        myQuaternion1->die();
	        myQuaternion2->die();
	    }
	    cout<<((float)t)/CLOCKS_PER_SEC<<" seconds.";
	}*/
	Quaternion * myQuaternion1 = Factory::createFromValues(1, 2, 3, 4);
	Quaternion * myQuaternion2;
	myQuaternion2 = Factory::updateQuaternion(myQuaternion1);
	//Quaternion * myQuaternion2 = Factory::createFromValues(2, 3, 4, 5);

	myQuaternion1->displayRM();
	return 0;
}
